
import { CCBoolean, Color, Component, Enum, Graphics, Node, NodePool, ScrollView, tween, UITransform, v3, Vec2, _decorator } from "cc";
import InfiniteCell from "./InfiniteCell";

const { ccclass, property } = _decorator;

enum Direction {
    vertical = 1,
    horizontal,
}

interface GetCellNumber {
    /**
     * 返回这个 List 中数据的总数量
     */
    (): number;
}

interface GetCellIdentifer {
    /**
     * 通过数据的下标返回这个 Cell 的表现类型的标志
     * @param dataIndex: 当前 Cell 所渲染的数据在列表中的下标
     */
    (dataIndex?: number): string;
}

interface GetCellSize {
    /**
     * 通过数据的下标返回这个 Cell 的尺寸（垂直 List 为高度，水平 List 为宽度）
     * @param dataIndex: 当前 Cell 所渲染的数据在列表中的下标
     */
    (dataIndex?: number): number;
}

interface GetCellView {
    /**
     * 获取一个 Cell 的 View 实例，记住这个控件必须已经挂在一个存在的 Node 上
     * @param dataIndex: 当前 Cell 所渲染的数据在列表中的下标
     * @param identifier: 这个 Cell 的表现类型标志
     * 
     * 这个回调函数只会出现在已经没有可以重用的 Cell 时，List 才会向这个函数请求新的 Cell 实例
     * 所有已经请求的 Cell 实例都会被重复利用。
     */
    (dataIndex?: number, identifier?: string): InfiniteCell;
}

interface GetCellData {
    /**
     * 根据一个 Cell 的下标获取一个 Cell 的数据，这个数据会作为 Cell 的 UpdateContent 的参数
     * 这个回调是可选的，可以不提供，如果不提供的话，Cell 需要自己在 UpdateContent 中向其他模块获取更新自己内容的数据
     */
    (dataIndex?: number): any;
}

interface OnLoadCompleted {
    /**
     *  table 加载完成，cellsOffset有数值
     */
    ()
}

interface OnScrollEnded {
    /**
     * 滚动结束，
     */
    (): void
}

interface OnScrollToBottom {
    /**
     * 滚动到底部，
     */
    (): void
}

interface OnDestroyed {
    /**
     *  table 销毁时，cellsOffset有数值
     */
    (cellIndex: number): void
}

export interface InitParam {
    getCellNumber: GetCellNumber,
    getCellSize: GetCellSize,
    getCellIdentifer: GetCellIdentifer,
    getCellView: GetCellView,
    getCellData?: GetCellData,
    onLoadCompleted?: OnLoadCompleted,
    onScrollEnded?: OnScrollEnded,
    onScrollToBottom?: OnScrollToBottom,
    onDestroyed?: OnDestroyed,
}

interface CellPools {
    [index: string]: NodePool;
}

@ccclass
export default class InfiniteList extends Component {
    @property({
        type: Enum(Direction),
        tooltip: "List 滚动的方向，可以选择垂直或者水平"
    })
    public direction = Direction.vertical;

    @property({
        tooltip: "cell 之间的像素间隔，最开始和最后面不会添加"
    })
    public spacing = 0;

    @property({ tooltip: "List 顶部（水平滚动则是最左边）的间隔空间" })
    public headPadding = 0;

    @property({ tooltip: "List 底部（水平滚动则是最右边）的间隔空间" })
    public bottomPadding = 0;

    @property({ tooltip: "侧边的间距，垂直滚动就是左右边的间距，水平滚动就是上下边的间距" })
    public sidePadding: Vec2 = new Vec2(0, 0);

    @property(CCBoolean)
    elastic: boolean = false;

    /**无特殊需求clearPool默认即可*/
    public Init(p: InitParam, clearPoool: boolean = false) {
        this._init(p, clearPoool);
    }

    public playMove(cellIndex: number, moveLength: number, bShow: boolean, bClear: boolean = false) {
        let actionTime = 0.1;
        let realThis = this;
        if (bShow) {
            realThis.Reload(true, bClear);
        } else {
            realThis.node.pauseSystemEvents(true);
            this.scheduleOnce(() => {
                realThis.Reload(true, bClear);
                realThis.node.resumeSystemEvents(true);
            }, actionTime);
        }
        let arTemp = new Array<InfiniteCell>();
        arTemp = arTemp.concat(this._activeCellViews);
        arTemp.sort((cell1: InfiniteCell, cell2: InfiniteCell): number => {
            if (cell1.node.position.y > cell2.node.position.y) {
                return -1;
            } else if (cell1.node.position.y < cell2.node.position.y) {
                return 1;
            }
            return 0;
        });
        for (let [index, data] of Array.from(arTemp.entries())) {
            if (index + arTemp[0].dataIndex > cellIndex) {
                if (bShow) {
                    data.node.setPosition(data.node.position.x, data.node.position.y + moveLength);
                    tween(data.node).by(actionTime, { position: v3(0, -moveLength, 0) });
                } else {
                    tween(data.node).by(actionTime, { position: v3(0, moveLength, 0) });
                }
            }
        }
    }

    /**
     * Reload 整个 List，这时获取数据的回调函数会重新触发一遍，所有的 cell 也会更新一遍内容
     */
    public Reload(keepPos: boolean = false, allclean: boolean = true) {
        this.clearAndStop(keepPos, allclean);
        this._load();
    }

    /**
     * 重新刷新当前显示 cell 的内容，不会重新载入整个列表
     * 所以如果列表的数据数量发生了变化，或是想要修改 Cell 的尺寸，调用 Refresh 是没有用处的，请调用 Reload
     */
    public Refresh() {
        this._updateActiveCellContent();
    }

    /**
     * 返回相对于 ScrollView 的这个 Cell 的滚动坐标
     * @param idx Cell 的索引下标
     */
    public GetScrollPosOfCell(idx: number): Vec2 {
        let sp = this._getCellPosOfIndex(idx);
        if (this.direction == Direction.vertical) {
            return new Vec2(0, sp);
        } else {
            return new Vec2(sp, 0);
        }
    }

    /**
     * 在规定的时间里滚动到指定的 Cell
     * @param idx 目标的 Cell 的下标
     */
    public ScrollToCell(idx: number, timeInSecond: number = 1, attenuated: boolean = true) {
        let pos = this.GetScrollPosOfCell(idx);
        this._scrollView.scrollToOffset(pos, timeInSecond, attenuated);
        if (timeInSecond == 0) {
            this._onScrolling();
        }
        // this._scrollView.scrollTo(pos, timeInSecond, attenuated);
    }
    public scrollToOffset(offset: Vec2, timeInSecond?: number, attenuated?: boolean) {
        this._scrollView.scrollToOffset(offset, timeInSecond, attenuated);
    }
    /**
     * 查看一个 Cell 是否当前可见
     * @param idx Cell 的下标
     */
    public IsCellVisible(idx: number): boolean {
        if (idx >= this._activeCellIndexRange.x && idx <= this._activeCellIndexRange.y) return true;
        else return false;
    }

    ////////////////////////////////////////////////////////////
    // Implenmentions
    ////////////////////////////////////////////////////////////

    private _debug = false;
    private _scrollView: ScrollView;
    private _content: Node;
    private _delegate: InitParam;
    private _inited = false;

    private _scrollPosition = 0;
    private _activeCellIndexRange: Vec2;
    private _cellPools: CellPools = {};

    private _cellsOffset: Array<number>;	// bottom side of cell position
    private _cellsSize: Array<number>;
    private _activeCellViews = new Array<InfiniteCell>();

    getAllCell(): Node[] {
        let ret = [];
        this._content.children.forEach((val: Node, index: number) => {
            ret[index] = val;
        });

        return ret;
    }

    public getScrollView(): ScrollView {
        return this._scrollView;
    }

    public onLoad() {
        // setup scrollview component
        this._scrollView = this.node.getComponent(ScrollView);
        if (!this._scrollView) {
            this._scrollView = this.node.addComponent(ScrollView);
            if (this.direction == Direction.horizontal) {
                this._scrollView.vertical = false;
                this._scrollView.horizontal = true;
            } else {
                this._scrollView.vertical = true;
                this._scrollView.horizontal = false;
            }
        }

        // setup content node(which is root of every cell)
        this._content = new Node();
        let transform = this._content.addComponent(UITransform);
        transform.setAnchorPoint(0, 1);

        this.node.addChild(this._content);
        this._scrollView.content = this._content;
        if (this._debug) {
            // set background color to content for debug use
            this._content.addComponent(Graphics);
        }

        // Everything OK, let's start
        this._inited = true;
        if (this._delegate) {
            this._load();
        }
    }

    public update() {
        if (this._debug) {
            let transform = this._content.getComponent(UITransform);

            let g = this._content.getComponent(Graphics);
            g.clear();
            g.fillColor = Color.YELLOW;
            g.fillRect(0, 0, transform.width, transform.height);
        }
    }

    public onEnable() {
        // bind event to scrollview
        this.node.on("scrolling", this._onScrolling, this);
        this.node.on("scroll-ended", this._onScrollEnded, this);
        this.node.on("scroll-to-bottom", this._onScrollToBottom, this);
    }

    public onDisable() {
        this.node.targetOff(this);
    }
    public setContentPos(slPos: number, x: number, y: number) {
        this._scrollPosition = slPos;
        this._content.setPosition(x, y);
        this._scrollView.stopAutoScroll();
        this._onScrolling();
    }
    private _onScrolling() {
        if (!this._delegate) return;
        const offset = this._scrollView.getScrollOffset();
        if (this.direction == Direction.vertical) {
            this._scrollPosition = offset.y;
        } else {
            this._scrollPosition = offset.x * -1;
        }

        // refresh active cell with new scroll position
        let cellCount: number = this._delegate.getCellNumber();
        if (cellCount > 0) {
            this._refreshActiveCells();
        }
    }

    private _onScrollEnded() {
        if (this._delegate && this._delegate.onScrollEnded) {
            this._delegate.onScrollEnded();
        }
    }

    private _onScrollToBottom() {
        if (this._delegate && this._delegate.onScrollToBottom) {
            this._delegate.onScrollToBottom();
        }
    }

    private _init(p: InitParam, allclean?: boolean) {
        let needClear = false;
        if (this._delegate) needClear = true;
        this._delegate = p;
        if (this._inited) {
            if (needClear) this.clearAndStop(false, allclean);
            this._load();
        }
    }

    public clearAndStop(keepPos: boolean = false, allclean: boolean = false) {
        if (this._activeCellViews) {
            while (this._activeCellViews.length > 0) {
                this._recycleCell(this._activeCellViews.length - 1, allclean);
            }
            if (allclean) { this._cellPools = {}; this._content.destroyAllChildren(); }
        }

        this._activeCellIndexRange = new Vec2(-1, -1);
        if (!keepPos) {
            this._scrollPosition = 0;
            if (this._content) {
                this._content.setPosition(0, 0);
            }
        }
    }

    private _load() {
        // get all cell offset with spacing and padding

        if (!this._delegate) { return; }
        const dataLen = this._delegate.getCellNumber();
        if (dataLen <= 0) { return; }

        let offset = this.headPadding;
        this._cellsOffset = new Array<number>(dataLen);
        this._cellsSize = new Array<number>(dataLen);
        for (let i = 0; i < dataLen; i++) {
            let s = this._delegate.getCellSize(i)
            this._cellsSize[i] = s;
            offset = s + (i == 0 ? 0 : this.spacing) + offset;
            this._cellsOffset[i] = offset;
        }
        offset += this.bottomPadding;

        let transform = this._content.getComponent(UITransform);
        if (this.direction == Direction.vertical) {
            transform.setContentSize(this.node.getComponent(UITransform).width, offset);
        } else {
            transform.setContentSize(offset, this.node.getComponent(UITransform).height);
        }

        // create visible cells
        const range = this._getActiveCellIndexRange();
        this._activeCellIndexRange = range;

        for (let i = range.x; i <= range.y; i++) {
            this._addCellView(i);
        }
        if (this._delegate && this._delegate.onLoadCompleted) {
            this._delegate.onLoadCompleted();
        }

        this.autoSetElasticState();
    }
    private _refreshActiveCells() {
        // update current active cells with new scroll position
        const range = this._getActiveCellIndexRange();
        // check if any cell need update
        if (range.equals(this._activeCellIndexRange)) return;

        // recycle all out of range cell
        let i = 0;
        while (i < this._activeCellViews.length) {
            let cell = this._activeCellViews[i];
            if (cell.dataIndex < range.x || cell.dataIndex > range.y) {
                this._recycleCell(i);
            } else {
                i++;
            }
        }

        // add any not exist cell
        // !TODO: boost this part effecient
        for (let i = range.x; i <= range.y; i++) {
            let needadd = true;
            for (let j = 0; j < this._activeCellViews.length; j++) {
                if (this._activeCellViews[j].dataIndex == i) {
                    needadd = false;
                    break;
                }
            }

            if (needadd) this._addCellView(i);
        }

        // update current active cell range
        this._activeCellIndexRange = range;
    }

    /**
     * remove one active cell from _activeCellViews array
     * @param cellIndex index of active cell views array
     */
    private _recycleCell(cellIndex: number, allclean: boolean = false) {
        // !TODO: need store this cell in node pool
        let cell = this._activeCellViews[cellIndex];
        let cellRootNode = null;
        if (cell.rootNode) {
            cellRootNode = cell.rootNode;
        }
        if (!cellRootNode) {
            cellRootNode = cell.node;
        }
        this._activeCellViews.splice(cellIndex, 1);
        cellRootNode.removeFromParent(false);
        if (allclean) {
            cellRootNode.destroy();
            if (cell) { cell.destroy(); }
            return;
        }
        cell.dataIndex = -1;
        if (!this._cellPools[cell.cellIdentifier]) {
            this._cellPools[cell.cellIdentifier] = new NodePool();
        }
        let pool = this._cellPools[cell.cellIdentifier];
        pool.put(cellRootNode);
    }

    private _getCellViewFromPool(id: string): InfiniteCell | null {
        if (!this._cellPools[id]) return null;
        let pool = this._cellPools[id];
        let cellNode: Node = pool.get();
        if (!cellNode) return null;
        return cellNode.getComponent(InfiniteCell);
    }

    /**
     * Return vector2 for start and end cell index of current scroll position
     */
    private _getActiveCellIndexRange(): Vec2 {
        let startPos = this._scrollPosition;
        let endPos = startPos + (this.direction == Direction.vertical ? this.node.getComponent(UITransform).height : this.node.getComponent(UITransform).width);
        return new Vec2(this._getCellIndexOfPos(startPos), this._getCellIndexOfPos(endPos));
    }

    private _getCellIndexOfPos(pos: number): number {
        // !TODO: boost this function speed by using binary search
        for (let i = 0; i < this._cellsOffset.length; i++) {
            if (this._cellsOffset[i] >= pos) return i;
        }
        return this._cellsOffset.length - 1;
    }

    /**
     * Get cell top position by its index
     * @param idx Cell index
     */
    private _getCellPosOfIndex(idx: number): number {
        return this._cellsOffset[idx] - this._cellsSize[idx];
    }

    private _addCellView(dataIndex: number) {
        let id = this._delegate.getCellIdentifer(dataIndex);
        let cell = this._getCellViewFromPool(id);
        if (!cell) {
            cell = this._delegate.getCellView(dataIndex);
            // cell.node.setAnchorPoint(0, 1);
            let cellRootNodeTmp = null;
            if (cell.rootNode) {
                cellRootNodeTmp = cell.rootNode;
            }
            if (!cellRootNodeTmp) {
                cellRootNodeTmp = cell.node;
            }
            this.setAnchorPoint(cellRootNodeTmp, 0, 1)
            cell.cellIdentifier = id;
        }

        let cellRootNode = null;
        if (cell.rootNode) {
            cellRootNode = cell.rootNode;
        }
        if (!cellRootNode) {
            cellRootNode = cell.node;
        }

        cell.dataIndex = dataIndex;
        cell.enabled = true;
        this._activeCellViews.push(cell)
        this._content.addChild(cellRootNode);

        if (this.direction == Direction.vertical) {
            cellRootNode.position.x = this.sidePadding.x;
            cellRootNode.position.y = (this._cellsOffset[cell.dataIndex] - this._cellsSize[cell.dataIndex]) * -1;
            cellRootNode.setContentSize(this.node.getComponent(UITransform).width - this.sidePadding.x - this.sidePadding.y, this._cellsSize[dataIndex]);
        } else {
            cellRootNode.position.x = (this._cellsOffset[cell.dataIndex] - this._cellsSize[cell.dataIndex]);
            cellRootNode.position.y = this.sidePadding.x * -1;
            cellRootNode.setContentSize(this._cellsSize[dataIndex], this.node.getComponent(UITransform).height - this.sidePadding.x - this.sidePadding.y);
        }

        cell.dataIndex = dataIndex;
        this._updateCellContent(cell);
    }

    private _updateActiveCellContent() {
        this._activeCellViews.forEach(cell => {
            this._updateCellContent(cell);
        });
    }

    private _updateCellContent(cell: InfiniteCell) {
        let data = null
        if (this._delegate.getCellData) {
            data = this._delegate.getCellData(cell.dataIndex);
        }

        cell.UpdateContent(data);
    }

    public stopImmediately() {
        if (this._scrollView && this._scrollView.isAutoScrolling()) {
            this._scrollView.stopAutoScroll();
        }
    }

    /**
     * 获取当前可见的第一个cell的索引
     */
    public getActiveCellIndex() {
        let index: number = 0;
        if (this._activeCellViews && this._activeCellViews.length > 0) {
            index = this._activeCellViews[0].dataIndex;
        }
        return index;
    }

    public getActiveCellsNumber() {
        return this._activeCellViews ? this._activeCellViews.length : 0;
    }

    onDestroy() {
        if (this._delegate && this._delegate.onDestroyed) {
            let cellIndex: number = this.getActiveCellIndex();
            this._delegate.onDestroyed(cellIndex);
        }
    }


    // 当内容少于tableview尺寸时，不能拉动及回弹
    public autoSetElasticState() {
        //使用这个组件的配置，UI上挂的scrollview的组件的这个属性不好使了

        /*
        let tableWidth = this.node.width;
        let tableHeight = this.node.height;
        let canElastic: boolean = true;
        if (this.direction == Direction.horizontal) {
            if (tableWidth > this._content.width) {
                canElastic = false;
            }
        }
        else {
            if (tableHeight > this._content.height) {
                canElastic = false;
            }
        }
        */
        this.setElasticState(this.elastic);
    }

    public setElasticState(state: boolean) {
        this._scrollView.elastic = state;
    }

    public getActiveCellIndexRange(): Array<number> {
        let cellRange: Array<number> = new Array<number>()
        let range: Vec2 = this._getActiveCellIndexRange();
        cellRange.push(range.x, range.y);
        return cellRange;
    }

    public getActiveViews(): Array<InfiniteCell> {
        return this._activeCellViews ? this._activeCellViews : null;
    }

    public setAnchorPoint(node: Node, anchorX: number, anchorY: number) {
        let dx = (anchorX - node.getComponent(UITransform).anchorX) * node.getComponent(UITransform).width;
        let dy = (anchorY - node.getComponent(UITransform).anchorY) * node.getComponent(UITransform).height;
        node.setPosition(node.position.x + dx, node.position.y + dy);

        for (let child of node.children) {
            child.setPosition(child.position.x + dx, child.position.y + dy);
        }
        node.getComponent(UITransform).setAnchorPoint(new Vec2(anchorX, anchorY));
    }


}